﻿// ----------------------------------------------------------------------
// <author name="Moses Kehengu"/>
// <copyright file="ReportingEngine.cs" company="Futures Group International">
//     Copyright statement. All right reserved
// </copyright>
//
// ------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Timers;
using GsmComm.GsmCommunication;
using System.Data;
using System.Data.Common;
using System.Security.Permissions;
using System.Security.Principal;
using ReportingEngine;
using System.Text.RegularExpressions;
using DataModels.Common;
using DataModels.Enumerations;
using GenericTools.ReportProcessing;
using GenericTools;
using Reporting;
using GenericTools.Models;
using DataModels.Models;
using DataModels.Models.Messaging;
using DataModels.Models.Context;

namespace ReportingEngine
{
    /// <summary>
    /// SMS Reporting engine
    /// </summary>
    public class Engine
    {
        private const string SETTINGS_KEY_MODEMS_CAN_SEND_REPLY = "MODEMS_CAN_SEND_REPLY";
        private const bool DEFAULT_MODEMS_CAN_SEND_REPLY = true;

        #region Class Variables
        private bool started = false;
        //private bool requireRestart = false;
        private bool allowSameMonthReporting = false;

        private Dictionary<Modem, int> modems = new Dictionary<Modem, int>();
        private List<Modem> disabledModems = new List<Modem>();
        private BackgroundWorker backgroundWorker;
        private Timer timer;

        private int m_TrialBeforeRestart = 2;  // the default is 30 times
        #endregion

        //public delegate void ChangingLogHandler(object sender, ReportingEngineEventArgs e);
        //public event ChangingLogHandler ChangingLog;
        public event EventHandler<ReportingEngineEventArgs> LogChanged;

        /// <summary>
        /// This event fires just before message processing begins
        /// </summary>
        public event EventHandler<Reporting.MessageEventArgs> ProcessingMessage;

        /// <summary>
        /// This event fires just before the message is about to be sent
        /// </summary>
        public event EventHandler<Reporting.MessageEventArgs> SendingReplyMessage;

        /// <summary>
        /// This event fires when message has been sent.
        /// </summary>
        public event EventHandler<Reporting.MessageEventArgs> MessageSent;

        #region Constructors
        public Engine(List<Modem> modems, bool allowSameMonthReporting)
        {
            //ChangingLog += new ChangingLogHandler(ReportingEngine_ChangingLog);
            this.allowSameMonthReporting = allowSameMonthReporting;
            this.modems.Clear();
            foreach (Modem modem in modems) this.modems[modem] = 0;

            if (modems.Count == 0) return;
            else
            {
                backgroundWorker = new BackgroundWorker();
                backgroundWorker.WorkerReportsProgress = true;
                backgroundWorker.WorkerSupportsCancellation = true;
                backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
                backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
                backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
            }

            // Internal events handling
            this.SendingReplyMessage += Engine_SendingReplyMessage;
        }
        #endregion

        #region Protected Methods
        protected virtual void OnLogChanged(ReportingEngineEventArgs e)
        {
            EventHandler<ReportingEngineEventArgs> handler = LogChanged;
            if (handler != null) handler(this, e);
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Starts reporting engine
        /// </summary>
        /// <returns></returns>
        public bool Start()
        {
            if (started) return true;
            else
            {
                Run();
                return true;
            }
        }

        /// <summary>
        /// Stops reporting engine
        /// </summary>
        public void Stop()
        {
            if (!started) return;
            try
            {
                if (backgroundWorker.IsBusy) backgroundWorker.CancelAsync();
            }
            catch { }
        }

        /// <summary>
        /// Close the reporting engine. This includes modem connection as well.
        /// </summary>
        public void Close()
        {
            if (started) Stop();
            try
            {
                foreach (Modem modem in modems.Keys)
                {
                    try
                    {
                        modem.Close();
                    }
                    catch { }
                }
            }
            catch { }
        }

        public void RemoveModem(Modem modem)
        {
            this.modems.Remove(modem);
        }

        public void AddModem(Modem modem)
        {
            this.modems.Add(modem, 0);
        }
        #endregion

        #region Public Properties
        public bool Started
        {
            get
            {
                return started;
            }
        }
        #endregion

        #region Private Helper Methods
        private void Run()
        {
            if (!backgroundWorker.IsBusy) backgroundWorker.RunWorkerAsync();
        }

        #region Background Worker Event Handlers
        private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            String log = String.Empty;
            if (e.ProgressPercentage == -1)
            {
                log = e.UserState.ToString();
                ReportingEngineEventArgs ev = new ReportingEngineEventArgs(log);
                //if (ChangingLog != null) ChangingLog(this, ev);
                OnLogChanged(ev);
            }

            if (e.ProgressPercentage == 100)
            {
                log = e.UserState.ToString();
                ReportingEngineEventArgs ev = new ReportingEngineEventArgs(log, ReportingEngineEventArgs.EventType.OPERATION_COMPLETED);
                //if (ChangingLog != null) ChangingLog(this, ev);
                OnLogChanged(ev);
            }
        }

        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {
                /*if (requireRestart)
                {
                    // Clear all existing modems
                    foreach (Modem modem in this.modems.Keys)
                    {
                        try { modem.Close(); }
                        catch { }
                    }

                    #region Trying to scan for hardware changes
                    backgroundWorker.ReportProgress(-1, "Trying to scan for hardware changes...");
                    try
                    {
                        System.AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
                        WindowsIdentity curIdentity = WindowsIdentity.GetCurrent();
                        WindowsPrincipal myPrincipal = new WindowsPrincipal(curIdentity);

                        int pdnDevInst = 0;
                        int result = Win32Api.CM_Locate_DevNodeA(ref pdnDevInst, null, Win32Api.CM_LOCATE_DEVNODE_NORMAL);
                        if (result == Win32Api.CR_SUCCESS)
                        {
                            result = Win32Api.CM_Reenumerate_DevNode(pdnDevInst, Win32Api.CM_REENUMERATE_NORMAL);
                            if (result == Win32Api.CR_ACCESS_DENIED)
                            {
                                backgroundWorker.ReportProgress(-1, "Access denied...");
                            }
                            if (result == Win32Api.CR_SUCCESS)
                            {
                                backgroundWorker.ReportProgress(-1, "Scanning operation completed successfully");
                            }
                            else
                            {
                                backgroundWorker.ReportProgress(-1, "Scanning operation did not complete");
                            }
                        }
                        else
                        {
                            backgroundWorker.ReportProgress(-1, "Could not acquire root device node");
                        }
                    }
                    catch { }
                    #endregion
                    requireRestart = false;
                }*/

                this.started = true;
                backgroundWorker.ReportProgress(-1, "Reporting Engine started...");
                // Adding disabled modems, who knows may they are working now...
                foreach (Modem modem in this.disabledModems) this.modems[modem] = 0;
                this.disabledModems.Clear();

                #region Some kind of fail safe for working Modems
                List<Modem> modemsToRemove = new List<Modem>();

#if DEBUG
                foreach (var modem in this.modems.Keys)
                {
                    backgroundWorker.ReportProgress(-1, String.Format("{0} is OK", modem.Name));
                }
#else
                foreach (Modem modem in this.modems.Keys)
                {
                    backgroundWorker.ReportProgress(-1, String.Format("{0}: Checking device communication", modem.Name));
                    try
                    {
                        if (modem.Communication.IsOpen())
                        {
                            if (modem.Communication.IsConnected())
                            {
                                backgroundWorker.ReportProgress(-1, String.Format("{0} is OK", modem.Name));
                            }
                            else
                            {
                                backgroundWorker.ReportProgress(-1, String.Format("{0}: Device failed to respond to commands", modem.Name));
                                modemsToRemove.Add(modem);
                            }
                        }
                        else
                        {
                            backgroundWorker.ReportProgress(-1, String.Format("{0}: Communication to device is not open", modem.Name));
                            modem.Communication.Open();
                            if (modem.Communication.IsOpen())
                            {
                                backgroundWorker.ReportProgress(-1, String.Format("{0}: Communication to device is now open", modem.Name));
                                if (modem.Communication.IsConnected())
                                {
                                    backgroundWorker.ReportProgress(-1, String.Format("{0} is OK", modem.Name));
                                }
                                else
                                {
                                    backgroundWorker.ReportProgress(-1, String.Format("{0}: Device failed to respond to commands", modem.Name));
                                    modemsToRemove.Add(modem);
                                }
                            }
                            else
                            {
                                backgroundWorker.ReportProgress(-1, String.Format("{0}: Failed to establish Communication to device", modem.Name));
                                modemsToRemove.Add(modem);
                            }
                        }
                    }
                    catch (ArgumentException) { }
                    catch (Exception ex)
                    {
                        backgroundWorker.ReportProgress(-1, String.Format("{0}: [Unknown Error] {1}", modem.Name, ex.Message));
                        modemsToRemove.Add(modem);
                    }
                }
#endif

                foreach (Modem modem in modemsToRemove)
                {
                    backgroundWorker.ReportProgress(-1, String.Format("Disabling modem: {0}", modem.Name));
                    this.disabledModems.Add(modem);
                    this.modems.Remove(modem);
                }

                if (this.modems.Keys.Count < 1)
                {
                    backgroundWorker.ReportProgress(-1, String.Format("There is no working modem"));
                    backgroundWorker.ReportProgress(100, "Reporting Engine stopped");
                    return;
                }
                #endregion

                #region Running Task Loop
                while (true)
                {
                    try
                    {
                        ReadMessages();
                        ProcessReceivedMessages();
                        SendQueuedMessages();

                        if (backgroundWorker.CancellationPending)
                        {
                            backgroundWorker.ReportProgress(-1, "Reporting Engine is closing, Please wait...");
                            backgroundWorker.ReportProgress(-1, "Closing all devices communication ports");
                            try
                            {
                                foreach (Modem modem in this.modems.Keys) modem.Close();
                            }
                            catch { }
                            break;
                        }
                        else
                        {
                            System.Threading.Thread.Sleep(AppConfiguration.ReportingEngineSMSCheckDelay);
                        }
                    }
                    catch (Exception ex)
                    {
                        backgroundWorker.ReportProgress(-1, String.Format("[Unknown Error]: {0}", ex.Message));
                        Utilities.LogError(ex, null);
                        /*try
                        {
                            ModemException modemEx = (ModemException)ex;
                            if (modemEx.Code == ModemException.RELOAD_DRIVER)
                            {
                                requireRestart = true;
                                // Closing all modem communications
                                backgroundWorker.ReportProgress(-1, "Closing all devices communication ports");
                                try
                                {
                                    foreach (Modem modem in this.modems.Keys) modem.Close();
                                }
                                catch { }
                            }
                            else requireRestart = false;
                        }
                        catch { }
                        break;*/
                    }
                }                
                #endregion

                if (backgroundWorker.CancellationPending)
                {
                    backgroundWorker.ReportProgress(-1, "Reporting Engine is closed");
                    try
                    {
                        foreach (Modem modem in this.modems.Keys) modem.Close();
                    }
                    catch { }
                    break;
                }
                /*
                if (requireRestart)
                {
                    backgroundWorker.ReportProgress(-1, "Reporting Engine stopped");
                }
                else
                {
                    backgroundWorker.ReportProgress(100, "Reporting Engine stopped");
                    break;
                }*/
            }
        }

        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.started = false;
            //if (requireRestart) Start();
        }
        #endregion

        #region Three Main Core Operations
        private void ReadMessages()
        {
            string storage = String.Empty;
            for (int i = 0; i < this.modems.Keys.Count; i++)
            {
                Modem modem = this.modems.Keys.ElementAt(i);
                storage = PhoneStorageType.Phone;
                modem.ReadMessages(storage);
                if (modem.LastError != null)
                {
                    string log = String.Format("[{0}]: Error Reading Messages - {1}", modem.Name, modem.LastError.Message);
                    backgroundWorker.ReportProgress(-1, log);

                    modems[modem]++;    // increment number of times error occured.
                    if (modems[modem] > m_TrialBeforeRestart) throw modem.LastError;
                }
                else modems[modem] = 0;

                storage = PhoneStorageType.Sim;
                modem.ReadMessages(storage);
                if (modem.LastError != null)
                {
                    string log = String.Format("[{0}]: Error Reading Messages - {1}", modem.Name, modem.LastError.Message);
                    backgroundWorker.ReportProgress(-1, log);
                    modems[modem]++;    // increment number of times error occured.
                    if (modems[modem] > m_TrialBeforeRestart) throw modem.LastError;
                }
                else modems[modem] = 0;
            }
        }

        private void ProcessReceivedMessages()
        {
            DbCommand command = GenericDataAccess.CreateCommand();
            command.CommandType = CommandType.Text;
            command.CommandText = @"SELECT [SMSIDNo],[CellNum],[Message],[Date] FROM SMSLog 
                WHERE Processed = 'FALSE' AND SMSType = 'FALSE'";
            DataTable dataTable = GenericDataAccess.ExecuteSelectCommand(command);
            foreach (DataRow row in dataTable.Rows)
            {
                int id = int.Parse(row["SMSIDNo"].ToString());
                string number = row["CellNum"].ToString();
                string message = row["Message"].ToString();
                if (string.IsNullOrWhiteSpace(message))
                {
                    SetMessageStatus(id, true);
                    continue;
                }

                DateTime date = DateTime.Parse(row["Date"].ToString());
                Modem modem = PreferedModem(number);

                bool okToContinue = false;
                string log = String.Empty;

                var e = new Reporting.MessageEventArgs
                {
                    PhoneNumber = number,
                    Message = message,
                    CancelOperation = false,
                    Type = MessageTypes.INCOMING
                };

                if (ProcessingMessage != null) ProcessingMessage(this, e);
                if (e.CancelOperation)
                {
                    string info = "Operation was canceled before message was processed.";
                    log = String.Format("[{0}]: {1}: [{2}] {3}", modem.Name, info,
                        modem.LastPhoneNumber, modem.LastSentMessage);
                    backgroundWorker.ReportProgress(-1, log);
                    SetMessageStatus(id, true);
                    continue;
                }

                string replyMessage = String.Empty;
                string keyword = String.Empty;

                // Finding message keyword (start with the 4 characters
                if (message.Length < 4) keyword = message.Trim().ToLower();
                else
                {
                    if (message.Trim().Contains('.')) keyword = message.Substring(0, 4).ToLower();
                    else keyword = message.Trim().ToLower();
                }

                SMSReportingFormat SMSFormat = SMSReportingFormat.HIGHLY_ADAPTIVE;
                DataInputingMethod inputMethod = DataInputingMethod.SMS;
                ReportingMode mode = ReportingMode.DEFAULT;
                switch (keyword)
                {
                    case "reg.":
                        #region Registration
                        backgroundWorker.ReportProgress(-1, String.Format("{0}: [{1}] {2}", 
                            "Starting processing Registration", number, message));
                        string registrationText = message.Substring(4);                                
                        RegistrationInfo registrationInfo = new RegistrationInfo(number, registrationText);
                        registrationInfo.Register();
                        backgroundWorker.ReportProgress(-1, String.Format("{0}: [{1}] {2}",
                            "Completed processing Registration", number, message));

                        //replyMessage = Utilities.GetTranslationText(number, registrationInfo.ErrorTextLabelID);
                        replyMessage = registrationInfo.ErrorText;
                        if (registrationInfo.Error == RegistrationInfo.RegistrationError.NO_ERROR)
                        {
                            //replyMessage = replyMessage.Replace("<USERNAME>", registrationInfo.RegisteredFullname);
                            //replyMessage = replyMessage.Replace("<SENDERNUM>", registrationInfo.SenderNum);
                            RemoveErrorMessage(number);
                        }
                        else
                        {
                            RecordErrorMessage(number, message, registrationInfo.FacilityCode,
                                null, null, null, registrationInfo.ErrorText, registrationInfo.ErrorTextLabelID);
                        }
                        okToContinue = modem.SendSMS(number, replyMessage, registrationInfo.HasError);
                        if (!okToContinue) GenericTools.ShortMessage.SendDBSMS(number, replyMessage);

                        string srr = "Sending Registration Reply";
                        if (modem.LastError != null)
                        {
                            string msg = modem.LastError.Message;
                            log = String.Format("[{0}]: {1}: [{2}] {3}", modem.Name, srr, number, msg);
                        }
                        else
                        {
                            log = String.Format("[{0}]: {1}: [{2}] {3}", modem.Name, srr, 
                                modem.LastPhoneNumber, modem.LastSentMessage);
                        }
                        backgroundWorker.ReportProgress(-1, log);
                        SetMessageStatus(id, true);
                        #endregion
                        break;

                    case "help":
                    case "msaada":
                    case "assistance":
                        #region Assistance
                        string a = "Starting processing Assistance Request";
                        backgroundWorker.ReportProgress(-1, String.Format("{0}: [{1}] {2}", a, number, message));
                        //okToContinue = Utilities.SendAssistance(number);
                        //if (okToContinue) SetMessageStatus(id, true);
                        Utilities.SendAssistance(number);
                        SetMessageStatus(id, true);
                        #endregion
                        break;

                    case "rep.":
                        #region Report Request
                        string sprr = "Starting processing Report Request";
                        backgroundWorker.ReportProgress(-1, String.Format("{0}: [{1}] {2}", sprr, number, message));

                        string reportingQuery = message.Substring(4).Trim();
                        ReportingType type = ReportingType.REQUEST;
                        if (allowSameMonthReporting) mode |= ReportingMode.ALLOW_SAME_MONTH_REPORTING;
                        ReportProcessor reportRequestProcessor = new ReportProcessor(number, reportingQuery, type, mode, SMSFormat, inputMethod);
                        replyMessage = reportRequestProcessor.GetReport();
                        string cprr = "Completed processing Report Request";
                        backgroundWorker.ReportProgress(-1, String.Format("{0}: [{1}] {2}", cprr, number, message));

                        if (reportRequestProcessor.HasError)
                        {
                            replyMessage = Utilities.GetTranslationText(number, reportRequestProcessor.ErrorTextLabelID);
                            RecordErrorMessage(number, message, reportRequestProcessor.FacilityCode,
                                reportRequestProcessor.Year, reportRequestProcessor.Period,
                                reportRequestProcessor.ReportID, replyMessage, reportRequestProcessor.ErrorTextLabelID);
                        }
                        else
                        {
                            RemoveErrorMessage(number);
                        }
                        okToContinue = modem.SendSMS(number, replyMessage, reportRequestProcessor.HasError);
                        if (!okToContinue) GenericTools.ShortMessage.SendDBSMS(number, replyMessage);
                        
                        string srrpr = "Sending Report Request processing Reply";
                        if (modem.LastError != null)
                        {
                            string m = modem.LastError.Message;
                            log = String.Format("[{0}]: {1}: [{2}] {3}", modem.Name, srrpr, number, m);
                        }
                        else 
                        {
                            log = String.Format("[{0}]: {1}: [{2}] {3}", modem.Name, srrpr, 
                                modem.LastPhoneNumber, modem.LastSentMessage);
                        }
                        backgroundWorker.ReportProgress(-1, log);
                        SetMessageStatus(id, true);
                        #endregion
                        break;

                    default:
                        #region Reporting and Others
                        string spr = "Starting processing Report";
                        backgroundWorker.ReportProgress(-1, String.Format("{0}: [{1}] {2}", spr, number, message));

                        string reportingText = message.Trim();   
                        if (allowSameMonthReporting) mode |= ReportingMode.ALLOW_SAME_MONTH_REPORTING;
                        
                        ReportProcessor reportProcessor = new ReportProcessor(number, reportingText, mode, SMSFormat, inputMethod);
                        reportProcessor.ReportBussinessRulesFailed += ReportProcessor.HandleMTUHAMCReport;
                        reportProcessor.SavingReport += ReportProcessor.HandleInconsitentReports;
                        reportProcessor.Submit();
                        
                        string cpr = "Completed processing Report";
                        backgroundWorker.ReportProgress(-1, String.Format("{0}: [{1}] {2}", cpr, number, message));

                        //replyMessage = Utilities.GetTranslationText(number, reportProcessor.ErrorTextLabelID);
                        replyMessage = reportProcessor.ErrorText;
                        bool errror = default(bool);
                        if (reportProcessor.HasError)
                        {
                            if (AppConfiguration.EnableStockInformation)
                            {
                                bool hasError = default(bool);
                                var reportingError = ReportProcessor.ProcessStockReportingMessage(number, reportingText, 
                                    replyMessage, reportProcessor.ErrorTextLabelID, out hasError);
                                replyMessage = reportingError.ErrorText;
                                errror = hasError;
                                //okToContinue = modem.SendSMS(number, replyMessage, hasError);
                                //if (!okToContinue) GenericTools.ShortMessage.SendDBSMS(number, replyMessage);
                                if (hasError)
                                {
                                    RecordErrorMessage(number, message, reportProcessor.FacilityCode,
                                        reportProcessor.Year, reportProcessor.Period,
                                        reportProcessor.ReportID, replyMessage, reportProcessor.ErrorTextLabelID);
                                    errror = reportProcessor.HasError;
                                }
                            }
                            else
                            {
                                RecordErrorMessage(number, message, reportProcessor.FacilityCode,
                                    reportProcessor.Year, reportProcessor.Period,
                                    reportProcessor.ReportID, replyMessage, reportProcessor.ErrorTextLabelID);
                                errror = reportProcessor.HasError;
                            }
                        }
                        else
                        {
                            RemoveErrorMessage(number);
                        }
                        
                        //okToContinue = modem.SendSMS(number, replyMessage, reportProcessor.HasError);
                        var messageEvent = new Reporting.MessageEventArgs
                        {
                            PhoneNumber = number,
                            Message = replyMessage,
                            CancelOperation = false,
                            Type = MessageTypes.OUTGOING_REPLY,
                            Tag = reportProcessor.ErrorTextLabelID
                        };

                        if (SendingReplyMessage != null) SendingReplyMessage(this, messageEvent);
                        if (messageEvent.CancelOperation)
                        {
                            string info = "Messaging operation was canceled.";
                            log = String.Format("[{0}]: {1}: [{2}] {3}", modem.Name, info,
                                messageEvent.PhoneNumber, messageEvent.Message);
                            backgroundWorker.ReportProgress(-1, log);
                            SetMessageStatus(id, true);
                            continue;
                        }

                        okToContinue = modem.SendSMS(number, replyMessage, errror);
                        if (!okToContinue) GenericTools.ShortMessage.SendDBSMS(number, replyMessage);

                        //if (AppConfiguration.EnableStockInformation)
                        //{
                        //    bool hasError = default(bool);
                        //    replyMessage = ProcessMessage(number, reportingText, out hasError);
                        //    okToContinue = modem.SendSMS(number, replyMessage, hasError);
                        //    if (!okToContinue) GenericTools.ShortMessage.SendDBSMS(number, replyMessage);
                        //}
                        //else
                        //{
                            
                        //}                        

                        string srpr = "Sending Report processing Reply";
                        if (modem.LastError != null)
                        {
                            string m = modem.LastError.Message;
                            log = String.Format("[{0}]: {1}: [{2}] {3}", modem.Name, srpr, number, m);
                        }
                        else 
                        {
                            log = String.Format("[{0}]: {1}: [{2}] {3}", modem.Name, srpr, 
                                modem.LastPhoneNumber, modem.LastSentMessage);
                        }
                        backgroundWorker.ReportProgress(-1, log);
                        SetMessageStatus(id, true);
                        #endregion
                        break;
                }
            }
        }

        private static void Engine_SendingReplyMessage(object sender, Reporting.MessageEventArgs e)
        {
            bool canSendReplyMesssage = true;
            string sCanSendReplyMessage = GenericTools.Utilities.GetSetting(SETTINGS_KEY_MODEMS_CAN_SEND_REPLY);
            if (string.IsNullOrEmpty(sCanSendReplyMessage))
            {
                GenericTools.Utilities.SetSetting(SETTINGS_KEY_MODEMS_CAN_SEND_REPLY, DEFAULT_MODEMS_CAN_SEND_REPLY);
                canSendReplyMesssage = DEFAULT_MODEMS_CAN_SEND_REPLY;
            }
            
            bool converted = bool.TryParse(sCanSendReplyMessage, out canSendReplyMesssage);
            if (!converted) canSendReplyMesssage = DEFAULT_MODEMS_CAN_SEND_REPLY;

            if (!canSendReplyMesssage)
            {
                e.CancelOperation = true;
                return;
            }

            if (e.Tag != null)
            {
                int knownError;
                converted = int.TryParse(e.Tag.ToString(), out knownError);
                if (converted)
                {
                    var log = new MessagingLog
                    {
                        PhoneNumber = e.PhoneNumber,
                        MessageBody = e.Message,
                        DateSendReceived = DateTime.Now,
                        IsMessageSentToUser = (e.Type & MessageTypes.OUTGOING) == MessageTypes.OUTGOING
                    };

                    using (var db = new IQSMS_TestDBContext())
                    {
                        bool found = (from u in db.OtherSystemUserNumbers
                                      where u.PhoneNumber == e.PhoneNumber
                                      select u).Count() == 1;
                        if (found) 
                        {
                            //TODO: Deal with situation where by the user of this other sub-system is 
                            // also a registered user in IQSMS

                            e.CancelOperation = true;
                            // Save the message for future reference.
                            db.OtherSystemMessagingLogs.Add(log);
                            db.SaveChanges();
                            return;
                        }
                    }                    
                }
            }
        }        

        private void SendQueuedMessages()
        {
            string log = String.Empty;
            DbCommand command = GenericDataAccess.CreateCommand();
            command.CommandType = CommandType.Text;
            command.CommandText = @"SELECT [SMSSID],[SenderNum],[TxtDetails] FROM [SendSMSQueue]";
            
            DataTable dataTable = GenericDataAccess.ExecuteSelectCommand(command, false);
            if (dataTable.Rows.Count > 0)
            {
                log = String.Format("Found {0} message{1} in database outbox", 
                    dataTable.Rows.Count, 
                    dataTable.Rows.Count > 1 ? "s" : String.Empty);
                backgroundWorker.ReportProgress(-1, log);
            }
            
            foreach (DataRow row in dataTable.Rows)
            {
                int id = int.Parse(row["SMSSID"].ToString());
                string number = row["SenderNum"].ToString();
                string message = row["TxtDetails"].ToString();
                
                Modem modem = PreferedModem(number);
                bool sent = modem.SendSMS(number, message, false);
                if (sent)
                {
                    log = String.Format("[{0}]: Sent: [{1}] {2}", modem.Name, number, message);
                    backgroundWorker.ReportProgress(-1, log);

                    //command = GenericDataAccess.CreateCommand();
                    command.CommandType = CommandType.Text;
                    command.CommandText = @"DELETE FROM [SendSMSQueue] WHERE [SMSSID]=@SMSSID";
                    command.Parameters.Clear();

                    DbParameter param = command.CreateParameter();
                    param.ParameterName = "@SMSSID";
                    param.DbType = DbType.Int32;
                    param.Value = id;
                    command.Parameters.Add(param);
                    GenericDataAccess.ExecuteNonQuery(command, false);

                    log = String.Format("Message ID: {0} - Removed from database outbox", id);
                    backgroundWorker.ReportProgress(-1, log);
                }
                else
                {
                    log = String.Format("Failed to send: [{0}]: [{1}] {2}", modem.Name, number, message);
                    backgroundWorker.ReportProgress(-1, log);

                    // Trying to send stored message using another connected modems
                    foreach (Modem m in this.modems.Keys)
                    {
                        if (m == modem) continue;
                        else
                        {
                            bool persisentlySent = m.SendSMS(number, message, false);
                            if (persisentlySent)
                            {
                                log = String.Format("[{0}]: Sent: [{1}] {2}", m.Name, number, message);
                                backgroundWorker.ReportProgress(-1, log);

                                //command = GenericDataAccess.CreateCommand();
                                command.CommandType = CommandType.Text;
                                command.CommandText = @"DELETE FROM [SendSMSQueue] WHERE [SMSSID]=@SMSSID";
                                command.Parameters.Clear();

                                DbParameter param = command.CreateParameter();
                                param.ParameterName = "@SMSSID";
                                param.DbType = DbType.Int32;
                                param.Value = id;
                                command.Parameters.Add(param);
                                GenericDataAccess.ExecuteNonQuery(command, false);

                                log = String.Format("Message ID: {0} - Removed from database outbox", id);
                                backgroundWorker.ReportProgress(-1, log);
                                break;
                            }
                            else
                            {
                                log = String.Format("Failed to send: [{0}]: [{1}] {2}", m.Name, number, message);
                                backgroundWorker.ReportProgress(-1, log);
                            }
                        }
                    }
                }
            }
            
            if (command.Connection.State != ConnectionState.Closed) command.Connection.Close();
        }
        #endregion

        private void SetMessageStatus(int messageID, bool processedStatus)
        {
            DbCommand command = GenericDataAccess.CreateCommand();
            command.CommandType = CommandType.Text;
            command.CommandText = @"UPDATE SMSLog SET Processed=@Status WHERE [SMSIDNo]=@SMSIDNo";

            DbParameter param = command.CreateParameter();
            param.ParameterName = "@Status";
            param.DbType = DbType.Boolean;
            param.Value = processedStatus;
            command.Parameters.Add(param);

            param = command.CreateParameter();
            param.ParameterName = "@SMSIDNo";
            param.DbType = DbType.Int32;
            param.Value = messageID;
            command.Parameters.Add(param);
            GenericDataAccess.ExecuteNonQuery(command);
        }

        private void RecordErrorMessage(string senderNumber, string message, string facilityCode, 
            int? year, int? period, int? repID, string errorMessage, int knownErrorCode)
        {
            // Avoiding null values and exceptions
            if (String.IsNullOrEmpty(senderNumber)) senderNumber = "UNIDENTIFIED";
            if (String.IsNullOrEmpty(message)) message = "<EMPTY>";
            if (String.IsNullOrEmpty(facilityCode)) facilityCode = "<EMPTY>";
            if (!year.HasValue) year = -1;
            if (!period.HasValue) period = -1;
            if (!repID.HasValue) repID = -1;
            if (String.IsNullOrEmpty(errorMessage)) errorMessage = "<EMPTY>";

            try
            {
                DbCommand command = GenericDataAccess.CreateCommand();
                command.CommandType = CommandType.Text;
                command.CommandText = @"INSERT INTO ErrMsgSMS(SenderNum,TxtDetails,DateReceived,
                FacCode,Year,Period,RepID,ErrMsg,Deleted, KnownReportingErrorId) VALUES(@SenderNum,@TxtDetails,GETDATE(),
                @FacCode,@Year,@Period,@RepID,@ErrMsg,@Deleted, @KnownReportingErrorId)";
                DbParameter param = command.CreateParameter();
                param.ParameterName = "@SenderNum";
                param.DbType = DbType.String;
                param.Value = senderNumber;
                command.Parameters.Add(param);

                param = command.CreateParameter();
                param.ParameterName = "@TxtDetails";
                param.DbType = DbType.String;
                param.Value = message;
                command.Parameters.Add(param);

                param = command.CreateParameter();
                param.ParameterName = "@FacCode";
                param.DbType = DbType.String;
                param.Value = facilityCode;
                command.Parameters.Add(param);

                param = command.CreateParameter();
                param.ParameterName = "@Year";
                param.DbType = DbType.Int32;
                param.Value = year;
                command.Parameters.Add(param);

                param = command.CreateParameter();
                param.ParameterName = "@Period";
                param.DbType = DbType.Int32;
                param.Value = period;
                command.Parameters.Add(param);

                param = command.CreateParameter();
                param.ParameterName = "@RepID";
                param.DbType = DbType.Int32;
                param.Value = repID;
                command.Parameters.Add(param);

                param = command.CreateParameter();
                param.ParameterName = "@ErrMsg";
                param.DbType = DbType.String;
                param.Value = errorMessage;
                command.Parameters.Add(param);

                param = command.CreateParameter();
                param.ParameterName = "@Deleted";
                param.DbType = DbType.Boolean;
                param.Value = false;
                command.Parameters.Add(param);

                param = command.CreateParameter();
                param.ParameterName = "@KnownReportingErrorId";
                param.DbType = DbType.Int32;
                param.Value = knownErrorCode;
                command.Parameters.Add(param);
                GenericDataAccess.ExecuteNonQuery(command);
            }
            catch { }
        }

        private void RemoveErrorMessage(string senderNumber)
        {
            try
            {
                DbCommand command = GenericDataAccess.CreateCommand();
                command.CommandType = CommandType.Text;
                command.CommandText = @"UPDATE ErrMsgSMS SET Deleted=1 WHERE SenderNum=@SenderNum";
                DbParameter param = command.CreateParameter();
                param.ParameterName = @"SenderNum";
                param.DbType = DbType.String;
                param.Value = senderNumber;
                command.Parameters.Add(param);
                GenericDataAccess.ExecuteNonQuery(command);
            }
            catch { }
        }

        private Modem PreferedModem(string number)
        {
            foreach (Modem m in this.modems.Keys)
            {
                List<string> prefices = m.PreferedPrefices;
                foreach (string prefix in prefices) if (number.StartsWith(prefix)) return m;
            }
            return this.modems.Keys.ElementAt(0);
        }
        #endregion
    }

}
